/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2.1 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2012 Live Networks, Inc.  All rights reserved.
// A class encapsulating the state of a MP3 stream
// Implementation

#include "MP3StreamState.hh"
#include "InputFile.hh"
#include "GroupsockHelper.hh"

#if defined(__WIN32__) || defined(_WIN32)
#define snprintf _snprintf
#endif

#define MILLION 1000000

MP3StreamState::MP3StreamState(UsageEnvironment& env)
: fEnv(env), fFid(NULL), fPresentationTimeScale(1)
{
}

MP3StreamState::~MP3StreamState()
{
	// Close our open file or socket:
	if (fFid != NULL && fFid != stdin) {
		if (fFidIsReallyASocket) {
			intptr_t fid_long = (intptr_t)fFid;
			closeSocket((int)fid_long);
		}
		else {
			CloseInputFile(fFid);
		}
	}
}

void MP3StreamState::assignStream(FILE* fid, unsigned fileSize)
{
	fFid = fid;

	if (fileSize == (unsigned)(-1)) { /*HACK#####*/
		fFidIsReallyASocket = 1;
		fFileSize = 0;
	}
	else {
		fFidIsReallyASocket = 0;
		fFileSize = fileSize;
	}
	fNumFramesInFile = 0; // until we know otherwise
	fIsVBR = fHasXingTOC = False; // ditto

	// Set the first frame's 'presentation time' to the current wall time:
	gettimeofday(&fNextFramePresentationTime, NULL);
}

struct timeval MP3StreamState::currentFramePlayTime() const
{
	unsigned const numSamples = 1152;
	unsigned const freq = fr().samplingFreq*(1 + fr().isMPEG2);

	// result is numSamples/freq
	unsigned const uSeconds
		= ((numSamples * 2 * MILLION) / freq + 1) / 2; // rounds to nearest integer

	struct timeval result;
	result.tv_sec = uSeconds / MILLION;
	result.tv_usec = uSeconds%MILLION;
	return result;
}

float MP3StreamState::filePlayTime() const
{
	unsigned numFramesInFile = fNumFramesInFile;
	if (numFramesInFile == 0) {
		// Estimate the number of frames from the file size, and the
		// size of the current frame:
		numFramesInFile = fFileSize / (4 + fCurrentFrame.frameSize);
	}

	struct timeval const pt = currentFramePlayTime();
	return numFramesInFile*(pt.tv_sec + pt.tv_usec / (float)MILLION);
}

unsigned MP3StreamState::getByteNumberFromPositionFraction(float fraction)
{
	if (fHasXingTOC) {
		// The file is VBR, with a Xing TOC; use it to determine which byte to seek to:
		float percent = fraction*100.0f;
		unsigned a = (unsigned)percent;
		if (a > 99) a = 99;

		unsigned fa = fXingTOC[a];
		unsigned fb;
		if (a < 99) {
			fb = fXingTOC[a + 1];
		}
		else {
			fb = 256;
		}
		fraction = (fa + (fb - fa)*(percent - a)) / 256.0f;
	}

	return (unsigned)(fraction*fFileSize);
}

void MP3StreamState::seekWithinFile(unsigned seekByteNumber)
{
	if (fFidIsReallyASocket) return; // it's not seekable

	SeekFile64(fFid, seekByteNumber, SEEK_SET);
}

unsigned MP3StreamState::findNextHeader(struct timeval& presentationTime)
{
	presentationTime = fNextFramePresentationTime;

	if (!findNextFrame()) return 0;

	// From this frame, figure out the *next* frame's presentation time:
	struct timeval framePlayTime = currentFramePlayTime();
	if (fPresentationTimeScale > 1) {
		// Scale this value
		unsigned secondsRem = framePlayTime.tv_sec % fPresentationTimeScale;
		framePlayTime.tv_sec -= secondsRem;
		framePlayTime.tv_usec += secondsRem*MILLION;
		framePlayTime.tv_sec /= fPresentationTimeScale;
		framePlayTime.tv_usec /= fPresentationTimeScale;
	}
	fNextFramePresentationTime.tv_usec += framePlayTime.tv_usec;
	fNextFramePresentationTime.tv_sec
		+= framePlayTime.tv_sec + fNextFramePresentationTime.tv_usec / MILLION;
	fNextFramePresentationTime.tv_usec %= MILLION;

	return fr().hdr;
}

Boolean MP3StreamState::readFrame(unsigned char* outBuf, unsigned outBufSize,
	unsigned& resultFrameSize,
	unsigned& resultDurationInMicroseconds)
{
	/* We assume that "mp3FindNextHeader()" has already been called */

	resultFrameSize = 4 + fr().frameSize;

	if (outBufSize < resultFrameSize) {
#ifdef DEBUG_ERRORS
		fprintf(stderr, "Insufficient buffer size for reading input frame (%d, need %d)\n",
			outBufSize, resultFrameSize);
#endif
		if (outBufSize < 4) outBufSize = 0;
		resultFrameSize = outBufSize;

		return False;
	}

	if (resultFrameSize >= 4) {
		unsigned& hdr = fr().hdr;
		*outBuf++ = (unsigned char)(hdr >> 24);
		*outBuf++ = (unsigned char)(hdr >> 16);
		*outBuf++ = (unsigned char)(hdr >> 8);
		*outBuf++ = (unsigned char)(hdr);

		memmove(outBuf, fr().frameBytes, resultFrameSize - 4);
	}

	struct timeval const pt = currentFramePlayTime();
	resultDurationInMicroseconds = pt.tv_sec*MILLION + pt.tv_usec;

	return True;
}

void MP3StreamState::getAttributes(char* buffer, unsigned bufferSize) const
{
	char const* formatStr
		= "bandwidth %d MPEGnumber %d MPEGlayer %d samplingFrequency %d isStereo %d playTime %d isVBR %d";
	unsigned fpt = (unsigned)(filePlayTime() + 0.5); // rounds to nearest integer
#if defined(IRIX) || defined(ALPHA) || defined(_QNX4) || defined(IMN_PIM) || defined(CRIS)
	/* snprintf() isn't defined, so just use sprintf() - ugh! */
	sprintf(buffer, formatStr,
		fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
		fpt, fIsVBR);
#else
	snprintf(buffer, bufferSize, formatStr,
		fr().bitrate, fr().isMPEG2 ? 2 : 1, fr().layer, fr().samplingFreq, fr().isStereo,
		fpt, fIsVBR);
#endif
}

void MP3StreamState::writeGetCmd(char const* hostName,
	unsigned short portNum,
	char const* fileName)
{
	char const* const getCmdFmt = "GET %s HTTP/1.1\r\nHost: %s:%d\r\n\r\n";

	if (fFidIsReallyASocket) {
		intptr_t fid_long = (intptr_t)fFid;
		int sock = (int)fid_long;
		char writeBuf[100];
#if defined(IRIX) || defined(ALPHA) || defined(_QNX4) || defined(IMN_PIM) || defined(CRIS)
		/* snprintf() isn't defined, so just use sprintf() */
		/* This is a security risk if filename can come from an external user */
		sprintf(writeBuf, getCmdFmt, fileName, hostName, portNum);
#else
		snprintf(writeBuf, sizeof writeBuf, getCmdFmt,
			fileName, hostName, portNum);
#endif
		send(sock, writeBuf, strlen(writeBuf), 0);
	}
	else {
		fprintf(fFid, getCmdFmt, fileName, hostName, portNum);
		fflush(fFid);
	}
}

// This is crufty old code that needs to be cleaned up #####
#define HDRCMPMASK 0xfffffd00

Boolean MP3StreamState::findNextFrame()
{
	unsigned char hbuf[8];
	unsigned l; int i;
	int attempt = 0;

read_again:
	if (readFromStream(hbuf, 4) != 4) return False;

	fr().hdr = ((unsigned long)hbuf[0] << 24)
		| ((unsigned long)hbuf[1] << 16)
		| ((unsigned long)hbuf[2] << 8)
		| (unsigned long)hbuf[3];

#ifdef DEBUG_PARSE
	fprintf(stderr, "fr().hdr: 0x%08x\n", fr().hdr);
#endif
	if (fr().oldHdr != fr().hdr || !fr().oldHdr) {
		i = 0;
	init_resync:
#ifdef DEBUG_PARSE
		fprintf(stderr, "init_resync: fr().hdr: 0x%08x\n", fr().hdr);
#endif
		if ((fr().hdr & 0xffe00000) != 0xffe00000
			|| (fr().hdr & 0x00060000) == 0 // undefined 'layer' field
			|| (fr().hdr & 0x0000F000) == 0 // 'free format' bitrate index
			|| (fr().hdr & 0x0000F000) == 0x0000F000 // undefined bitrate index
			|| (fr().hdr & 0x00000C00) == 0x00000C00 // undefined frequency index
			|| (fr().hdr & 0x00000003) != 0x00000000 // 'emphasis' field unexpectedly set
			) {
			/* RSF: Do the following test even if we're not at the
		   start of the file, in case we have two or more
		   separate MP3 files cat'ed together:
		   */
			/* Check for RIFF hdr */
			if (fr().hdr == ('R' << 24) + ('I' << 16) + ('F' << 8) + 'F') {
				unsigned char buf[70 /*was: 40*/];
#ifdef DEBUG_ERRORS
				fprintf(stderr,"Skipped RIFF header\n");
#endif
				readFromStream(buf, 66); /* already read 4 */
				goto read_again;
			}
			/* Check for ID3 hdr */
			if ((fr().hdr & 0xFFFFFF00) == ('I' << 24) + ('D' << 16) + ('3' << 8)) {
				unsigned tagSize, bytesToSkip;
				unsigned char buf[1000];
				readFromStream(buf, 6); /* already read 4 */
				tagSize = ((buf[2] & 0x7F) << 21) + ((buf[3] & 0x7F) << 14) + ((buf[4] & 0x7F) << 7) + (buf[5] & 0x7F);
				bytesToSkip = tagSize;
				while (bytesToSkip > 0) {
					unsigned bytesToRead = sizeof buf;
					if (bytesToRead > bytesToSkip) {
						bytesToRead = bytesToSkip;
					}
					readFromStream(buf, bytesToRead);
					bytesToSkip -= bytesToRead;
				}
#ifdef DEBUG_ERRORS
				fprintf(stderr,"Skipped %d-byte ID3 header\n", tagSize);
#endif
				goto read_again;
			}
			/* give up after 20,000 bytes */
			if (i++ < 20000/*4096*//*1024*/) {
				memmove(&hbuf[0], &hbuf[1], 3);
				if (readFromStream(hbuf + 3, 1) != 1) {
					return False;
				}
				fr().hdr <<= 8;
				fr().hdr |= hbuf[3];
				fr().hdr &= 0xffffffff;
#ifdef DEBUG_PARSE
				fprintf(stderr, "calling init_resync %d\n", i);
#endif
				goto init_resync;
			}
#ifdef DEBUG_ERRORS
			fprintf(stderr,"Giving up searching valid MPEG header\n");
#endif
			return False;

#ifdef DEBUG_ERRORS
			fprintf(stderr,"Illegal Audio-MPEG-Header 0x%08lx at offset 0x%lx.\n",
				fr().hdr,tell_stream(str)-4);
#endif
			/* Read more bytes until we find something that looks
		   reasonably like a valid header.  This is not a
		   perfect strategy, but it should get us back on the
		   track within a short time (and hopefully without
		   too much distortion in the audio output).  */
			do {
				attempt++;
				memmove(&hbuf[0], &hbuf[1], 7);
				if (readFromStream(&hbuf[3], 1) != 1) {
					return False;
				}

				/* This is faster than combining fr().hdr from scratch */
				fr().hdr = ((fr().hdr << 8) | hbuf[3]) & 0xffffffff;

				if (!fr().oldHdr)
					goto init_resync;       /* "considered harmful", eh? */

			} while ((fr().hdr & HDRCMPMASK) != (fr().oldHdr & HDRCMPMASK)
				&& (fr().hdr & HDRCMPMASK) != (fr().firstHdr & HDRCMPMASK));
#ifdef DEBUG_ERRORS
			fprintf (stderr, "Skipped %d bytes in input.\n", attempt);
#endif
		}
		if (!fr().firstHdr) {
			fr().firstHdr = fr().hdr;
		}

		fr().setParamsFromHeader();
		fr().setBytePointer(fr().frameBytes, fr().frameSize);

		fr().oldHdr = fr().hdr;

		if (fr().isFreeFormat) {
#ifdef DEBUG_ERRORS
			fprintf(stderr,"Free format not supported.\n");
#endif
			return False;
		}

#ifdef MP3_ONLY
		if (fr().layer != 3) {
#ifdef DEBUG_ERRORS
			fprintf(stderr, "MPEG layer %d is not supported!\n", fr().layer);
#endif
			return False;
		}
#endif
	}

	if ((l = readFromStream(fr().frameBytes, fr().frameSize))
		!= fr().frameSize) {
		if (l == 0) return False;
		memset(fr().frameBytes + 1, 0, fr().frameSize - 1);
	}

	return True;
}

static Boolean socketIsReadable(int socket)
{
	const unsigned numFds = socket + 1;
	fd_set rd_set;
	FD_ZERO(&rd_set);
	FD_SET((unsigned)socket, &rd_set);
	struct timeval timeout;
	timeout.tv_sec = timeout.tv_usec = 0;

	int result = select(numFds, &rd_set, NULL, NULL, &timeout);
	return result != 0; // not > 0, because windows can return -1 for file sockets
}

static char watchVariable;

static void checkFunc(void* /*clientData*/)
{
	watchVariable = ~0;
}

static void waitUntilSocketIsReadable(UsageEnvironment& env, int socket)
{
	while (!socketIsReadable(socket)) {
		// Delay a short period of time before checking again.
		unsigned usecsToDelay = 1000; // 1 ms
		env.taskScheduler().scheduleDelayedTask(usecsToDelay,
			(TaskFunc*)checkFunc, (void*)NULL);
		watchVariable = 0;
		env.taskScheduler().doEventLoop(&watchVariable);
		// This allows other tasks to run while we're waiting:
	}
}

unsigned MP3StreamState::readFromStream(unsigned char* buf,
	unsigned numChars)
{
	// Hack for doing socket I/O instead of file I/O (e.g., on Windows)
	if (fFidIsReallyASocket) {
		intptr_t fid_long = (intptr_t)fFid;
		int sock = (int)fid_long;
		unsigned totBytesRead = 0;
		do {
			waitUntilSocketIsReadable(fEnv, sock);
			int bytesRead
				= recv(sock, &((char*)buf)[totBytesRead], numChars - totBytesRead, 0);
			if (bytesRead < 0) return 0;

			totBytesRead += (unsigned)bytesRead;
		} while (totBytesRead < numChars);

		return totBytesRead;
	}
	else {
		waitUntilSocketIsReadable(fEnv, (int)fileno(fFid));
		return fread(buf, 1, numChars, fFid);
	}
}

#define XING_FRAMES_FLAG       0x0001
#define XING_BYTES_FLAG        0x0002
#define XING_TOC_FLAG          0x0004
#define XING_VBR_SCALE_FLAG    0x0008

void MP3StreamState::checkForXingHeader()
{
	// Look for 'Xing' in the first 4 bytes after the 'side info':
	if (fr().frameSize < fr().sideInfoSize) return;
	unsigned bytesAvailable = fr().frameSize - fr().sideInfoSize;
	unsigned char* p = &(fr().frameBytes[fr().sideInfoSize]);

	if (bytesAvailable < 8) return;
	if (p[0] != 'X' || p[1] != 'i' || p[2] != 'n' || p[3] != 'g') return;

	// We found it.
	fIsVBR = True;

	u_int32_t flags = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
	unsigned i = 8;
	bytesAvailable -= 8;

	if (flags&XING_FRAMES_FLAG) {
		// The next 4 bytes are the number of frames:
		if (bytesAvailable < 4) return;
		fNumFramesInFile = (p[i] << 24) | (p[i + 1] << 16) | (p[i + 2] << 8) | (p[i + 3]);
		i += 4; bytesAvailable -= 4;
	}

	if (flags&XING_BYTES_FLAG) {
		// The next 4 bytes is the file size:
		if (bytesAvailable < 4) return;
		fFileSize = (p[i] << 24) | (p[i + 1] << 16) | (p[i + 2] << 8) | (p[i + 3]);
		i += 4; bytesAvailable -= 4;
	}

	if (flags&XING_TOC_FLAG) {
		// Fill in the Xing 'table of contents':
		if (bytesAvailable < XING_TOC_LENGTH) return;
		fHasXingTOC = True;
		for (unsigned j = 0; j < XING_TOC_LENGTH; ++j) {
			fXingTOC[j] = p[i + j];
		}
		i += XING_TOC_FLAG; bytesAvailable -= XING_TOC_FLAG;
	}
}
